home *** CD-ROM | disk | FTP | other *** search
/ BBS in a Box 12 / BBS in a box XII-2.iso / Files II / Prog / B-C / C++Source Code Fmtr.sit / Src / cdent.cp / cdent.cp
Encoding:
Text File  |  1992-04-28  |  15.5 KB  |  739 lines  |  [TEXT/MPS ]

  1. #ifndef __CDENT__
  2. #include "cdent.h"
  3. #endif
  4.  
  5. #ifndef __DFILE__
  6. #include "DFile.h"
  7. #endif
  8.  
  9. #ifndef __FORMATLOG__
  10. #include "FormatLog.h"
  11. #endif
  12.  
  13. #ifndef __CSCANNER__
  14. #include "CScanner.h"
  15. #endif
  16.  
  17. #ifndef __FORMATSTRINGS__
  18. #include "FormatStrings.h"
  19. #endif
  20.  
  21. #ifndef __FORMATTING__
  22. #include "Formatting.h"
  23. #endif
  24.  
  25. #ifndef __PARSER__
  26. #include "Parser.h"
  27. #endif
  28.  
  29. #ifndef __STDFILE__
  30. #include "StdFile.h"
  31. #endif
  32.  
  33. #ifndef __STACK__
  34. #include "Stack.h"
  35. #endif
  36.  
  37. #ifndef __CURSORCTL__
  38. #include <CursorCtl.h>
  39. #endif
  40.  
  41. #ifndef __NEW__
  42. #include <new.h>
  43. #endif
  44.  
  45. #ifndef __SEGLOAD__
  46. #include <segload.h>
  47. #endif
  48.  
  49. #ifndef __STDARG__
  50. #include <stdarg.h>
  51. #endif
  52.  
  53. #ifndef __STDLIB__
  54. #include <stdlib.h>
  55. #endif
  56.  
  57. #ifndef __CTYPE__
  58. #include <ctype.h>
  59. #endif
  60.  
  61.  
  62. #pragma segment ToolMain
  63.  
  64. extern pascal void _DATAINIT();
  65.  
  66.  
  67. Severity gDiagLevel = kDiagnostic;                // Diagnostics and more
  68.  
  69.  
  70. //ƒ-
  71. static const char  gVersion[] = "cdent v0.0a0";
  72. static const char* gInputFilename  = "standard input";
  73. static const char* gOutputFilename = 0;
  74. static FormatLog   gFormatLog;                // For saving commands
  75. static CScanner    gScanner;                // That which scans
  76. static Formatting  gFormat;                
  77. static Parser       gParser;                    // The one global parser
  78. static Stack       gFormatStack;            // Save formatting info around "#if"
  79. static Stack       gParserStack;            // Save parse state around "#if"
  80.  
  81. static Boolean    gOption_x = false;            // true if "-x" specified
  82. static Boolean    gOption_y = false;            // true if "-y" specified
  83. static Boolean    gOption_z = false;            // true if "-z" specified
  84. //ƒ+
  85.  
  86.  
  87. static void doOption(int argc, char *argv[], int &i);
  88. static Boolean isArg(int argc, int i, const char *option);
  89. static void badOption(const char *arg);
  90. static int getNumber(int argc, char *argv[], int i);
  91. static Boolean isFormatOption(int argc, char *argv[], int i);
  92. static char *lower(register char *aString);
  93. static Boolean ustreql(const char *a, const char *b);
  94.  
  95.  
  96. // main
  97. #pragma segment ToolMain
  98. void main(int argc, char *argv[])
  99. {
  100.     StdFile * nullFile = new StdFile("Dev:Null", "w");
  101.     StdFile * inputFile;
  102.     StdFile * outputFile = &gStdout;
  103.     short anErr;
  104.     int i;
  105.     DFile outputDFile;
  106.  
  107.  
  108.     // Unload the initialization segment
  109.     UnloadSeg(_DATAINIT);
  110.  
  111.     // Initialize the cursor
  112.     InitCursorCtl(0);
  113.  
  114.     // Initialize the global data objects
  115.     gFormatLog.IFormatLog();
  116.     gScanner.ICScanner();
  117.     gFormat.IFormatting();
  118.     gParser.IParser();
  119.     gFormatStack.IStack(16, 4);
  120.     gParserStack.IStack(16, 4);
  121.  
  122.     /*
  123.     ** Open the input file
  124.     */
  125.     setvbuf(stderr, 0, _IOFBF, BUFSIZ);            // To make dumps faster
  126.     inputFile = &gStdin;
  127.     gParser.DebugParser(false);
  128.  
  129.     // Construct the DFile used for output
  130.     outputDFile.IDFile(64, 64);
  131.     outputDFile.SetOutput(outputFile);
  132.     gFormat.SetOutput(&outputDFile);
  133.  
  134.     for (i = 1; i < argc; i++) {
  135.         if (argv[i][0] == '-') {
  136.             doOption(argc, argv, i);
  137.         } else {
  138.             gInputFilename = argv[i];
  139.             inputFile = new StdFile(argv[i], "r");
  140.             if (inputFile == 0 || (FILE *)inputFile == NULL)
  141.                 diag(kFatal, "Could not open %s\n", argv[i]);
  142.         }
  143.     }
  144.  
  145.  
  146.     /*
  147.     ** Check if any special case options were checked
  148.     */
  149.     if (gOption_x) {
  150.     }
  151.  
  152.     if (gOption_y) {
  153.     }
  154.  
  155.     if (gOption_z) {
  156.     }
  157.  
  158.     // Re-apply any specific glue
  159.     for (i = 1; i < argc; i++)
  160.         if (argv[i][0] == '-')
  161.             isFormatOption(argc, argv, i);
  162.  
  163.  
  164.     /*
  165.     ** Allocate the buffer for it and read it in
  166.     */
  167.     size_t inputSize = inputFile->GetSize();
  168.  
  169.     if (inputSize == 0)
  170.         inputSize = 1024;
  171.  
  172.     DataArea inputArea;
  173.     anErr = inputArea.IDataArea();
  174.     if (anErr != noErr)
  175.         diag(kFatal, "Could not allocate memory for file (%d)\n", anErr);
  176.  
  177.     size_t amtRead = 1;
  178.     while (amtRead != 0) {
  179.         // Allocate space for the data
  180.         if (inputArea.Require(inputSize) < inputSize)
  181.             diag(kFatal, "Could not allocate memory while reading\n");
  182.  
  183.         // Read the data into the area allocate, advance the cursor
  184.         inputArea.HLock();
  185.         amtRead = inputFile->Read(*inputArea.GetHandle(), inputSize);
  186.         inputArea.HUnlock();
  187.         inputArea.IncrCursor(amtRead);
  188.  
  189.         // When nothing else remains to be read, write a terminating null
  190.         // and lock the buffer out of the way.
  191.         if (amtRead == 0) {
  192.             char aZero = 0;
  193.  
  194.             inputArea.Write(&aZero, sizeof(aZero));
  195.             inputArea.Truncate();
  196.             inputArea.MoveHHi();
  197.             inputArea.HLock();
  198.         }
  199.     }
  200.     inputFile->Close();
  201.  
  202.     // Point to the input buffer
  203.     char *inputBuffer = (char *) * inputArea.GetHandle();
  204.  
  205.  
  206.     /*
  207.     ** Open the output file if gOutputFilename has been set.
  208.     */
  209.     if (gOutputFilename != 0) {
  210.         outputFile = new StdFile(gOutputFilename, "w");
  211.         if (outputFile == 0)
  212.             diag(kFatal, "Could not open output file %s\n", gOutputFilename);
  213.     }
  214.  
  215.  
  216.     /*
  217.     ** Scan and display the results.
  218.     **    "done" is true when an eof has been read.  "currentOutputFile" is
  219.     ** normally  &gStdout.  It is controlled by comments (kSLex_Comment)
  220.     ** whose minor types  are either kSLex_CommentFormatOn or
  221.     ** kSLex_CommentFormatOff.  When formatting is on, display is done by
  222.     ** the parser.  When formatting is off, the variable "lastBufferEnd" is
  223.     ** the text position of the next character after the just scanned token.
  224.     ** We display the text (via  gStdout.Write()) starting at lastBufferEnd
  225.     ** upto the end of the token.  lastBufferEnd is then updated.
  226.     */
  227.  
  228.     // Construct the DFile used for output
  229.     StdFile * currentOutputFile = outputFile;
  230.     outputDFile.SetOutput(currentOutputFile);
  231.  
  232.     // Fix up the input buffer
  233.     inputBuffer[inputSize] = '\0';
  234.     gScanner.ScanThis(inputBuffer, inputSize);
  235.     gParser.SetFormatting(&gFormat);
  236.     gFormat.SetFormatLog(&gFormatLog);
  237.  
  238.     Boolean done = false;
  239.     size_t lastBufferEnd = 0;                    // Shut up the compiler
  240.  
  241.     while (!done) {
  242.         Syntactic * aToken = gScanner.NextToken();
  243.  
  244.         switch (aToken->Type()) {
  245.         case kSErr:
  246.             diag(kFatal, "Bad token near: %40s\n", gScanner.Text());
  247.             break;
  248.  
  249.         case kSLex_EOF:
  250.             gParser.Parse();
  251.             gFormat.Display(aToken);
  252.             done = true;
  253.             break;
  254.  
  255.         case kSLex_PoundLine:
  256.             // Flush any partial states in the parser, assuring that tokens
  257.             // are emitted.  Then make sure that the PoundLine is on a fresh
  258.             // line by itself.
  259.             gParser.Parse();
  260.             gFormat.ExecuteGlue((FormatString)"&n");
  261.  
  262.             // Handle a preprocessor line.  The MinorType() of the token describes
  263.             // which type of preprocessor line it is.
  264.             switch (aToken->MinorType()) {
  265.             case kSLex_PoundIf:
  266.                 // Save the current formatting and parsing info
  267.                 {
  268.                     Formatting * newFormatting = new Formatting;
  269.                     Parser * newParser = new Parser;
  270.  
  271.                     newFormatting->IFormatting(&gFormat);
  272.                     newParser->IParser(&gParser);
  273.  
  274.                     gFormatStack.Push(newFormatting);
  275.                     gParserStack.Push(newParser);
  276.                 }
  277.                 break;
  278.  
  279.             case kSLex_PoundElif:
  280.             case kSLex_PoundElse:
  281.             case kSLex_PoundEndIf:
  282.                 // Restore the last saved formatting & parsing info when a
  283.                 // #else or #elif is scanned.  If a #endif is scanned, we
  284.                 // keep using the parsing information from the preceding
  285.                 // conditional then or conditional else part.  This will
  286.                 // minimize the number of parse errors which occur when users
  287.                 // do obscene things like put closing parens/curlys in both
  288.                 // arms of the conditional compile expression.
  289.                 // If this is a #endif, then pop the stacks of saved information
  290.                 // and destruct them
  291.                 if (gFormatStack.IsEmpty())
  292.                     diag(kFatal, "Unbalanced conditional compilation: not enough #if\n");
  293.  
  294.                 if (aToken->MinorType() == kSLex_PoundEndIf) {
  295.                     delete(Formatting * )gFormatStack.Pop();
  296.                     delete(Parser * )gParserStack.Pop();
  297.                 } else {
  298.                     // Make damn sure the copy constructor is called...
  299.                     // Make gFormat write to the current outputFile
  300.                     gFormat.IFormatting((Formatting *)gFormatStack.Top());
  301.                     gParser.IParser((Parser *)gParserStack.Top());
  302.                     outputDFile.SetOutput(currentOutputFile);
  303.                 }
  304.                 break;
  305.             }
  306.  
  307.             if (currentOutputFile != nullFile)
  308.                 gParser.Parse(aToken);
  309.             else {
  310.                 // Display from the last token to this token
  311.                 size_t endOfToken = gScanner.CharPos() + gScanner.TokenLength();
  312.                 outputFile->Write(&inputBuffer[lastBufferEnd], endOfToken - lastBufferEnd);
  313.                 lastBufferEnd = endOfToken;
  314.             }
  315.             break;
  316.  
  317.         default:
  318.             // Always let the parser see the token, even if its output is
  319.             // the null file.
  320.             if (!gParser.Parse(aToken)) {
  321.                 gFormat.Flush();
  322.                 if (gParser.DebugParser())
  323.                     gParser.DumpStack(gStderr);
  324.                 diag(kFatal, "Syntax Error");
  325.             }
  326.  
  327.             // Write the token if the output is the null file.
  328.             if (currentOutputFile == nullFile) {
  329.                 // Display from the last token to this token
  330.                 size_t endOfToken = gScanner.CharPos() + gScanner.TokenLength();
  331.                 outputFile->Write(&inputBuffer[lastBufferEnd], endOfToken - lastBufferEnd);
  332.                 lastBufferEnd = endOfToken;
  333.             }
  334.  
  335.             // Check if this is a comment controlling formatting
  336.             if (aToken->Type() == kSLex_Comment) {
  337.                 switch (aToken->MinorType()) {
  338.                 case kSLex_CommentFormatOff:
  339.                     gParser.Parse();
  340.                     currentOutputFile = nullFile;
  341.                     outputDFile.SetOutput(currentOutputFile);
  342.                     lastBufferEnd = gScanner.CharPos() + gScanner.TokenLength();
  343.                     break;
  344.  
  345.                 case kSLex_CommentFormatOn:
  346.                     currentOutputFile = outputFile;
  347.                     outputDFile.SetOutput(currentOutputFile);
  348.                     break;
  349.                 }
  350.             } else if (aToken->Type() == kSLex_NewLine)
  351.                 SpinCursor(7);
  352.             break;
  353.         }
  354.     }
  355.  
  356.     // Write any unwritten output
  357.     gFormat.Flush();
  358.     exit(EXIT_SUCCESS);
  359. }
  360.  
  361.  
  362.  
  363. // diag
  364. #pragma segment ToolMain
  365. void diag(Severity aLevel, const char *aFmt, ...)
  366. {
  367.     va_list ap;
  368.  
  369.     va_start(ap, aFmt);
  370.     if (aLevel >= gDiagLevel) {
  371.         gFormat.Flush();                        // Force any output
  372.         if (aLevel >= kDiagnostic)
  373.             fputs("# ", stderr);
  374.         vfprintf(stderr, aFmt, ap);
  375.     }
  376.  
  377.     if (aLevel >= kFatal) {
  378.         fprintf(stderr, "\nFile %s; Line %d\n", gInputFilename, gScanner.LineNumber());
  379.         exit(9);
  380.     }
  381.  
  382.     va_end(ap);
  383. }
  384.  
  385.  
  386.  
  387.  
  388. /*
  389. ** doOption
  390. ** Process option argv[i].  The index "i" is passed by reference to allow
  391. ** it to be advanced if other arguments must be consumed.
  392. */
  393. #pragma segment ToolMain
  394. static void doOption(int argc, char *argv[], int &i)
  395. {
  396.     const char *option = argv[i] + 1;            // Advance past the '-'
  397.  
  398.     if (option[1] == '\0') {
  399.         // Single character flag
  400.         switch (tolower(option[0])) {
  401.         case 'o':                                // -o    Output file
  402.             if (!isArg(argc, i + 1, option))
  403.                 return;
  404.             gOutputFilename = argv[++i];
  405.             break;
  406.  
  407.         case 'p':                                // -p    Report progress
  408.             gDiagLevel = kProgress;
  409.             diag(kProgress, "%s\n", gVersion);
  410.             break;
  411.  
  412.         case 't':                                // -t    Set the tab width
  413.             gFormat.TabSize(getNumber(argc, argv, i));
  414.             i++;
  415.             break;
  416.  
  417.         case 'x':                                // -x    No spaces around operators
  418.             gOption_x = true;
  419.             break;
  420.  
  421.         case 'y':                                // -y    No spaces around assignment
  422.             gOption_y = true;
  423.             break;
  424.  
  425.         case 'z':                                // -y    No space after ","
  426.             gOption_z = true;
  427.             break;
  428.  
  429.         default:
  430.             badOption(option);
  431.             break;
  432.         }
  433.     } else {
  434.         // Multi character option
  435.         enum EOption {
  436.             kOptNone                            //
  437.             , kOpt_indent                        //
  438.             , kOpt_linelength                    //
  439.             , kOpt_commentcol                    //
  440.             , kOpt_allnl                        //
  441.             , kOpt_nonl                            //
  442.             , kOpt_trace                        //
  443.         };
  444.  
  445.  
  446.         struct Option {
  447.             const char *fOptionName;            // Name of the option
  448.             EOption fOptionIndex;                // Integer of option
  449.             char fOptionNArgs;                    // # of args required.
  450.         };
  451.  
  452.  
  453.         static Option options[] = {
  454.                                    //ƒ-
  455.               {"indent",        kOpt_indent,        1}
  456.             , {"ll",            kOpt_linelength,    1}
  457.             , {"cc",            kOpt_commentcol,    1}
  458.             , {"allnl",            kOpt_allnl,            0}
  459.             , {"nonl",            kOpt_nonl,            0}
  460.             , {"trace",            kOpt_trace,            1}
  461.             , {0,                kOptNone,            0}        // Terminator
  462.             //ƒ+
  463.                                    };
  464.  
  465.         const Option *p = options;
  466.  
  467.         // Find the option.
  468.         while (p->fOptionName && !ustreql(option, p->fOptionName))
  469.             p++;
  470.  
  471.         // Check that there are enough arguments
  472.         if (!isArg(argc, i + p->fOptionNArgs, option))
  473.             return;
  474.  
  475.         // Process the option
  476.         switch (p->fOptionIndex) {
  477.         case kOpt_indent:
  478.             gFormat.SetIndent(getNumber(argc, argv, i));
  479.             break;
  480.  
  481.         case kOpt_linelength:
  482.             gFormat.LineLength(getNumber(argc, argv, i));
  483.             break;
  484.  
  485.         case kOpt_commentcol:
  486.             gFormat.CommentColumn(getNumber(argc, argv, i));
  487.             break;
  488.  
  489.         case kOpt_allnl:
  490.             gFormat.PassSourceNewLines(true);
  491.             gFormat.PassConsecutiveNewLines(false);
  492.             break;
  493.  
  494.         case kOpt_nonl:
  495.             gFormat.PassSourceNewLines(false);
  496.             gFormat.PassConsecutiveNewLines(false);
  497.             break;
  498.  
  499.         case kOpt_trace:
  500.             gDiagLevel = kDebug;
  501.             if (ustreql(argv[i + 1], "parse")) {
  502.                 gParser.DebugParser(true);
  503.             } else if (ustreql(argv[i + 1], "formatting")) {
  504.                 gFormat.DebugFormatting(true);
  505.                 gParser.DebugFormatting(true);
  506.             } else
  507.                 diag(kFatal, "Bad option to -trace: %s\n", argv[i + 1]);
  508.             break;
  509.  
  510.         default:
  511.             if (isFormatOption(argc, argv, i))
  512.                 i++;
  513.             else
  514.                 badOption(option);
  515.             break;
  516.         }
  517.  
  518.         // Option processing done.  Bump "i" by the number of arguments
  519.         // consumed by the option.
  520.         i += p->fOptionNArgs;
  521.     }
  522. }
  523.  
  524.  
  525.  
  526. /*
  527. ** isFormatOption
  528. ** Return true if the option passed in refers to one of the formats
  529. */
  530. #define CASE(x)        {#x, &gFS_##x},
  531.  
  532. #pragma segment ToolMain
  533. static Boolean isFormatOption(int argc, char *argv[], int i)
  534. {
  535.     if (i + 1 >= argc)
  536.         return (false);
  537.  
  538.     const char *aName = argv[i] + 1;
  539.     const char *aFormat = lower(argv[i + 1]);
  540.  
  541.     static struct Option2 {
  542.         const char *fName;
  543.         FormatString *fString;
  544.     } options[] = {
  545.         //ƒ-
  546.         CASE(if0)
  547.         CASE(if1)
  548.         CASE(if2)
  549.         CASE(if3)
  550.         CASE(if4)
  551.         CASE(if5)
  552.         CASE(if6)
  553.         CASE(if7)
  554.         
  555.         CASE(else1)
  556.         CASE(else3)
  557.         CASE(else4)
  558.         CASE(else6)
  559.         
  560.         CASE(switch0)
  561.         CASE(switch1)
  562.         CASE(switch2)
  563.         CASE(switch3)
  564.         CASE(switch4)
  565.         CASE(switch5)
  566.         CASE(switch7)
  567.         
  568.         CASE(for0)
  569.         CASE(for1)
  570.         CASE(for3)
  571.         CASE(for4)
  572.         CASE(for5)
  573.         CASE(for6)
  574.         CASE(for7)
  575.         CASE(for8)
  576.         
  577.         CASE(while0)
  578.         CASE(while1)
  579.         CASE(while2)
  580.         CASE(while3)
  581.         CASE(while4)
  582.         CASE(while5)
  583.         CASE(while6)
  584.         
  585.         CASE(do0)
  586.         CASE(do1)
  587.         CASE(do2)
  588.         CASE(do3)
  589.         CASE(do4)
  590.         CASE(do5)
  591.         CASE(do6)
  592.         
  593.         CASE(struct0)
  594.         CASE(struct1)
  595.         CASE(struct2)
  596.         CASE(struct3)
  597.         CASE(struct5)
  598.         CASE(struct6)
  599.         CASE(struct7)
  600.         
  601.         CASE(fundef2)
  602.         CASE(fundef3)
  603.         CASE(fundef4)
  604.         CASE(fundef6)
  605.         CASE(fundef7)
  606.         CASE(fundef8)
  607.         CASE(fundef10)
  608.         CASE(fundef11)
  609.         CASE(fundef12)
  610.         CASE(fundef13)
  611.         CASE(fundef14)
  612.         
  613.         CASE(decl0)
  614.         CASE(decl1)
  615.         CASE(decl2)
  616.         CASE(decl3)
  617.         CASE(decl5)
  618.         CASE(decl6)
  619.         CASE(decl7)
  620.         CASE(decl8)
  621.         CASE(decl9)
  622.         CASE(decl10)
  623.         CASE(decl11)
  624.         
  625.         CASE(stmt0)
  626.         CASE(stmt1)
  627.         
  628.         CASE(expr1)
  629.         CASE(expr2)
  630.         CASE(expr3)
  631.         CASE(expr4)
  632.         CASE(expr5)
  633.         CASE(expr6)
  634.         CASE(expr7)
  635.         CASE(expr8)
  636.         
  637.         CASE(funcall2)
  638.         CASE(funcall3)
  639.         CASE(funcall4)
  640.         
  641.         CASE(label1)
  642.         
  643.         CASE(block1)
  644.         CASE(block2)
  645.         
  646.         CASE(name1)
  647.         
  648.         CASE(goto1)
  649.         CASE(return1)
  650.         CASE(break1)
  651.         {0, 0}
  652.         //ƒ+
  653.         };
  654.  
  655.     for (struct Option2 *anOption = options; anOption->fName; anOption++)
  656.         if (ustreql(anOption->fName, aName)) {
  657.             *anOption->fString = (FormatString)aFormat;
  658.             return (true);
  659.         }
  660.  
  661.     return (false);
  662. }
  663.  
  664.  
  665.  
  666. // isArg
  667. #pragma segment ToolMain
  668. static Boolean isArg(int argc, int i, const char *option)
  669. {
  670.     if (i < argc)
  671.         return (true);
  672.  
  673.     diag(kFatal, "Missing %d argument%s to option \"-%s\"\n", i - argc + 1, (i == argc) ? "" : "s", option);
  674.     return (false);
  675. }
  676.  
  677.  
  678. // getNumber
  679. #pragma segment ToolMain
  680. static int getNumber(int argc, char *argv[], int i)
  681. {
  682.     if (isArg(argc, i + 1, argv[i])) {
  683.         int n;
  684.  
  685.         if (sscanf(argv[i + 1], "%d", &n) == 1)
  686.             return (n);
  687.         diag(kFatal, "Non-numeric argument to option -%s: %s\n", argv[i], argv[i + 1]);
  688.     }
  689.  
  690.     return (-1);
  691. }
  692.  
  693.  
  694.  
  695. // badOption
  696. #pragma segment ToolMain
  697. static void badOption(const char *arg)
  698. {
  699.     diag(kFatal, "Unrecognized option \"-%s\"\n", arg);
  700. }
  701.  
  702.  
  703.  
  704. // lower
  705. #pragma segment ToolMain
  706. static char *lower(register char *aString)
  707. {
  708.     char *p = aString;
  709.  
  710.     while (*p) {
  711.         *p = tolower(*p);
  712.         p++;
  713.     }
  714.  
  715.     return (aString);
  716. }
  717.  
  718.  
  719.  
  720.  
  721. /*
  722. ** ustreql
  723. **    Perform an unsigned comparison of the two strings.  Return true
  724. **    if they are equal, false if they are unequal
  725. */
  726. #pragma segment ToolMain
  727. static Boolean ustreql(const char *a, const char *b)
  728. {
  729.     if (a != b)
  730.         while (*a && *b && tolower(*a) == tolower(*b)) {
  731.             ++a;
  732.             ++b;
  733.         }
  734.  
  735.     return (*a == *b);
  736. }
  737.  
  738.  
  739.